const ccxt = require("ccxt");
const { BollService, LogService, Api } = require("./../service/index");
const {
  // BollStatus,
  OrderDirection,
  StoreName,
  BuildConfig,
  OrderStatus,
  Symbol
  // OrderDirectionDesc
} = require("./../constants");
const Decimal = require("decimal.js");
const _ = require("lodash");

const print = LogService.print;

class binanceV3 {
  constructor() {
    this.state = {
      exchangeId: "binance",
      apiKey:
        "H0ZUKjWcx07XH7wqDleIRE1WFm0EACFEQ6cuBsStU0P5B9EE7FLk9KzBM6X540mp",
      secret:
        "VaVcLYpAc5jdjzCnENWjE7Asx1e1uv8wEEWFIiayKJcS55YCUENl7WORmg4gXLZj",
      tick: "BTC/USDT",
      symbol: Symbol,
      minBollWidthRatio: 0.002,
      interval: "3m",
      loopDuration: 3000,
      transactionType: "future",
      leverage: 95,
      isMock: false,
      ratioCloseToBoll: 0.8,
      info: [],
      protectModeIndex: 12,
    };
  }

  main = () => {
    Promise.all([
      // 获取当前最新价格
      Api.requestPublicPrice(),
      // 查询账户余额
      Api.requestBalance(),
      // 查询开单列表
      Api.requestOpenOrders(),
      // 查询布林带数据
      Api.requestBoll(),
      // 查询最近50条K线蜡烛
      Api.requestKlinesV2(),
      // 查询是否越过RSI
      Api.requestRSI(),
      // 查询是否越过StochRSI
      Api.requestStochRSI(),
      // 查询开单列表
      Api.requestBuildOrderList(),
      // 查询合约价格
      Api.requestPrice(),
      // 查询持有单 => 获取持有方向
      Api.requestHoldingOrder(),
      // 取消多余止盈单，每次只留最新3单
      Api.cancelSomeStopOrders(),
      Api.requestOrderByType(['TAKE_PROFIT']),
    ]).then(
      ([
        price,
        {
          balance, // 总余额
          holdingAmount: _holdingAmount, // 持仓金额
          openOrderAmount, // 挂单金额
          availableBalance: freeAmount, // 可用余额（未下单的钱）
          isFree: _isFree, // 是否空仓
        },
        openOrders,
        {
          bollUp,
          bollMiddle,
          bollDown,
          bollWidth,
          lastBollUp,
          lastBollMiddle,
          lastBollDown,
          bollList,
        },
        klineList,
        { isRSIOverUp, isRSIOverDown, RSI },
        { isStochRSIOverUp, isStochRSIOverDown, stochRSI },
        buildOrderList,
        futurePrice,
        // 持有的方向
        { holdingDirection }
      ]) => {
        const {
          minBollWidthRatio,
          isMock,
          ratioCloseToBoll,
          protectModeIndex,
        } = this.state;
        const minBollWidth = Decimal(price).mul(minBollWidthRatio).toNumber();
        const bollStatus = BollService.getCurrentStatus({
          bollList,
          klineList,
          minBollWidth,
          price,
          isJudgeWidthIncrease: false,
        });

        let buildItemIndex = 0;
        buildOrderList.forEach((item) => {
          if (item.status === OrderStatus.FILLED) {
            buildItemIndex++;
          }
        });
        const cacheBuildList = LogService.getJson(StoreName.BUILD_LIST);
        const buildItem = cacheBuildList[buildItemIndex - 1] || {};
        const realAvgPrice = buildItem.realAvgPrice;

        const holdingAmount = isMock ? 10000 : _holdingAmount;
        const isFree = isMock ? false : _isFree;
        this.state.info = {
          price,
          holdingAmount,
          openOrderAmount,
          freeAmount,
          isFree,
          openOrders,
          bollUp,
          bollMiddle,
          bollWidth,
          bollDown,
          lastBollUp,
          lastBollMiddle,
          lastBollDown,
          bollList,
          klineList,
          minBollWidth,
          bollStatus,
          isRSIOverUp,
          isRSIOverDown,
          isStochRSIOverUp,
          isStochRSIOverDown,
          balance,
          RSI,
          stochRSI,
          ratioCloseToBoll,
          protectModeIndex,
          futurePrice,
          buildItemIndex,
          cacheBuildList,
          buildItem,
          realAvgPrice,
          stopInterval: () => {
            this.state.isStop = true;
          },
          goInterval: () => {
            this._interval = setTimeout(this.main, this.state.loopDuration);
            this.state.isStop = false;
          },
          holdingDirection
        };
        Api.addState(this.state.info);
        LogService.initFormatLog(this.state.info);

        if (isFree) {
          if (!openOrders.length) {
            // 空仓中，且无挂单
            this._handleFree();
          } else {
            // 已结束，还有挂单
            this._handleClosing();
          }
        } else {
          if (!holdingAmount) {
            // 挂单中
            this._handleOpening();
          } else {
            // 持有中
            this._handleHolding();
          }
        }

        if (!this.state.isStop) {
          this._interval = setTimeout(this.main, this.state.loopDuration);
        }
      }
    );
  };
  // 空仓中
  _handleFree() {
    const {
      freeAmount,
      bollStatus: currentBollStatus,
      bollList,
      klineList,
      bollWidth,
      minBollWidth,
      price,
      ratioCloseToBoll,
    } = this.state.info;
    const direction = BollService.getShakeOpenOrderDirection(
      price,
      bollList,
      ratioCloseToBoll
    );
    const xFactor = BollService.getShakeXFactor(
      bollList,
      direction,
      currentBollStatus
    );
    LogService.addParams({
      xFactor,
    });
    if (bollWidth > minBollWidth) {
      switch (direction) {
        case OrderDirection.BUY:
          print("震荡接近下轨，多单建仓", "order");
          Api.requestBuildingOrders(
            OrderDirection.BUY,
            freeAmount,
            currentBollStatus,
            xFactor
          );
          break;

        case OrderDirection.SELL:
          print("震荡接近上轨，空单建仓", "order");
          Api.requestBuildingOrders(
            OrderDirection.SELL,
            freeAmount,
            currentBollStatus,
            xFactor
          );
          break;

        default:
          print("震荡中", "normal");
          break;
      }
    } else {
      print(
        "布林带太窄，没有利润空间。俗话说得好，弱水三千，我只取一瓢，算啦不做了",
        "normal"
      );
    }
  }
  // 开仓中
  _handleOpening() {
    const {
      bollUp,
      bollWidth,
      bollDown,
      price,
      openOrders,
      cacheBuildList,
      buildItemIndex,
    } = this.state.info;

    if (
      openOrders[0] &&
      cacheBuildList[0] &&
      openOrders[0].price !== cacheBuildList[0].buildPrice &&
      openOrders.length < cacheBuildList.length
    ) {
      // 挂单已平仓，撤单
      Api.requestBalance().then(({ holdingAmount }) => {
        if (!holdingAmount) {
          print("挂单已平仓，撤单", "normal");
          Api.requestCancelAll();
        } else {
          print("哦，还在持有中", "normal");
        }
      });
    } else if (
      price <
        Decimal(bollUp)
          .sub(bollWidth * 0.5)
          .toNumber() &&
      price >
        Decimal(bollDown)
          .add(bollWidth * 0.5)
          .toNumber()
    ) {
      Api.requestBalance().then(({ holdingAmount }) => {
        if (!holdingAmount) {
          // 回到布林轨道内，撤单
          print("回到布林轨道正常范围，撤单", "normal");
          Api.requestCancelAll();
        } else {
          print("刚挂单就买进去了", "normal");
        }
      });
    } else {
      print("挂单中，未成交", "normal");
    }
  }

  // 持有中
  _handleHolding() {
    const {
      klineList,
      bollList,
      bollMiddle,
      minBollWidth,
      bollWidth,
      isRSIOverUp,
      isRSIOverDown,
      isStochRSIOverUp,
      isStochRSIOverDown,
      price,
      holdingAmount,
      balance,
      bollStatus,
      openOrders,
      protectModeIndex,
      buildItemIndex,
      futurePrice,
      buildItem,
      holdingDirection
    } = this.state.info;

    const { leverage } = this.state;

    const orderBollStatus = LogService.getState(
      StoreName.HOLDING_ORDER_BOLL_STATUS
    );
    // 用holdingAmount兜底
    const orderBollBtcQuantity = Math.max(
      Decimal(holdingAmount).mul(leverage).div(price).mul(1.1).toNumber(),
      0.001
    );
    const direction = LogService.getState(StoreName.HOLDING_ORDER_DIRECTION) || holdingDirection;

    const protectPrice = _.get(buildItem, "protectInfo.stopPrice");
    const protectStr = protectPrice ? `（重仓，${protectPrice}止盈）` : "";
    const holdingRatio = Decimal(holdingAmount).div(balance).toNumber();
    const buildAvgPrice = _.get(buildItem, "buildAvgPrice");
    // const buyMax = Math.max(bollMiddle - (bollWidth * 0.1), buildAvgPrice + buildAvgPrice * 0.0015 - (futurePrice - price))
    // const sellMax = Math.min(bollMiddle + (bollWidth * 0.1), buildAvgPrice - buildAvgPrice * 0.0015 - (futurePrice - price))
    const buyMax = bollMiddle - bollWidth * 0.1;
    const buyMin = bollMiddle + bollWidth * 0.1;
    const sellMax = bollMiddle + bollWidth * 0.1;
    const sellMin = bollMiddle - bollWidth * 0.1;
    const nowBuyAvgPrice =
      buildAvgPrice + buildAvgPrice * 0.002 - (futurePrice - price);
    const nowSellAvgPrice =
      buildAvgPrice - buildAvgPrice * 0.002 - (futurePrice - price);
    // 这里要考虑成本价，尽量少亏一点
    // 做多，如果成本价高于前置止盈价，取后置止盈价
    const buyStop = nowBuyAvgPrice > buyMax ? buyMin : buyMax;
    // 做空，如果成本价低于前置止盈价，取后置止盈价
    const sellStop = nowSellAvgPrice < sellMax ? sellMin : sellMax;

    switch (direction) {
      case OrderDirection.BUY:
        if (price > buyStop - BuildConfig.STOP_GAP) {
          print("震荡多单回归，平仓！", "order");
          Api.requestEndOrderNow(orderBollBtcQuantity);
        } else {
          if (
            holdingRatio > 0.07 &&
            protectPrice &&
            futurePrice > protectPrice - BuildConfig.STOP_GAP
          ) {
            print(
              `震荡多单仓位过重，虽未到中线，但有一定利润，止盈！`,
              "order"
            );
            Api.requestEndOrderNow(orderBollBtcQuantity);
          } else {
            print("震荡多单持有中" + protectStr, "normal");
          }
        }
        break;
      case OrderDirection.SELL:
        if (price < sellStop + BuildConfig.STOP_GAP) {
          print("震荡空单回归，平仓！", "order");
          Api.requestEndOrderNow(orderBollBtcQuantity);
        } else {
          if (
            holdingRatio > 0.07 &&
            protectPrice &&
            futurePrice < protectPrice + BuildConfig.STOP_GAP
          ) {
            print(
              `震荡空单仓位过重，虽未到中线，但有一定利润，止盈！`,
              "order"
            );
            Api.requestEndOrderNow(orderBollBtcQuantity);
          } else {
            print("震荡空单持有中" + protectStr, "normal");
          }
        }
        break;
      default:
        break;
    }
  }

  // 结单中
  _handleClosing() {
    // 撤销所有挂单
    Api.requestCancelAll();
  }

  _login() {
    const {
      exchangeId,
      interval,
      symbol,
      tick,
      apiKey,
      secret,
      leverage,
    } = this.state;
    const exchangeClass = ccxt[exchangeId],
      exchange = new exchangeClass({
        apiKey,
        secret,
        password: "031612",
        timeout: 30000,
        enableRateLimit: true,
        options: {
          defaultType: this.state.transactionType,
          type: this.state.transactionType,
          adjustForTimeDifference: true,
        },
      });
    this.$exchange = exchange;
    Api.setState({
      exchange,
      tick,
      interval,
      symbol,
      exchangeId,
      leverage,
    });
    // 设倍率
    Api.requestSetLeverage(leverage);
  }

  start() {
    const { exchangeId, tick } = this.state;
    // 登录
    this._login();
    console.log(`———— 【Joyo-v1】开始监听${exchangeId}：${tick}行情 ————`);
    // 轮询
    this._interval = setTimeout(this.main, this.state.loopDuration);
    // Api.requestBuildingOrders(OrderDirection.BUY, 579, 'shake', 10000)
    // const buildingList = MathModel.getBuildingList({
    // 	openPrice: 63000,
    // 	direction: OrderDirection.SELL,
    // 	leverage: this.state.leverage,
    // 	freeAmount: 271
    // })
  }
}

module.exports = new binanceV3();
